package cn.lycode.framework.web.service;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaSessionCustomUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.lycode.common.constant.Constants;
import cn.lycode.framework.constant.FrameworkConstant;
import cn.lycode.system.domain.vo.SysMenuVO;
import cn.lycode.system.service.SysMenuService;
import cn.lycode.system.service.SysRoleService;
import cn.lycode.common.utils.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static cn.lycode.framework.constant.FrameworkConstant.*;

/**
 * 用户权限处理
 *
 * 用户对应角色， 角色对应权限和菜单
 * 每次更新角色的菜单 清除 Perms_List、Menu_List
 * 每次更新用户对应的角色，应该清除当前用户的 Role_List
 * @author lyCode
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SysPermissionService {
    private final SysRoleService roleService;
    private final SysMenuService menuService;

    /**
     * 获取用户 角色权限信息
     * @param userId 用户ID
     * @return 角色权限信息
     */
    public Set<String> getRolePermFlag(Long userId) {
        SaSession session = StpUtil.getSessionByLoginId(userId);
        //session.get 方法有Sa-token提供缓存
        return session.get(SESSION_KEY_ROLE_LIST, () -> {
            Set<String> roles = new HashSet<String>();
            // 管理员拥有所有权限
            if (SecurityUtils.isSuperAdmin(userId)) {
                roles.add(Constants.SUPER_ADMIN);
            } else {
                roles.addAll(roleService.selectRoleNameListByUserId(userId));
            }
            return roles;
        });
    }

    /**
     * 获取用户的 菜单权限
     * @param userId 用户Id
     * @return 菜单权限信息
     */
    public Set<String> getMenuPermission(Long userId) {
        //需要校验用户权限时（访问带权限注解的function），会查询用户权限码集合
        //直接从内存（缓存）中读取用户权限集合即可。
        // 如果用户权限集合在登录后发生变化。 需要有更新机制更新内存（缓存）中用户权限集合
        // 缓存中放的应该是，用户——角色  角色——菜单权限  而不是用户——菜单权限
        Set<String> permissionSet = new HashSet<>();
        //遍历角色列表，查询拥有的权限码
        Set<String> rolePermFlagSet = getRolePermFlag(userId);
        if (rolePermFlagSet.contains(Constants.SUPER_ADMIN)) {
            permissionSet.add(Constants.ALL_PERMISSION);
        }else {
            for (String roleFlag : rolePermFlagSet) {
                SaSession roleSession = SaSessionCustomUtil.getSessionById(SESSION_KEY_ROLE_PREFIX + roleFlag);
                //roleSession.get 方法有Sa-token提供缓存
                List<String> list = roleSession.get(SESSION_KEY_PERMS_LIST, () -> {
                    return new ArrayList<>(menuService.selectMenuPermsByRoleFlag(roleFlag));     // 从数据库查询这个角色所拥有的权限列表
                });
                permissionSet.addAll(list);
            }
        }
        return permissionSet;
    }

    /**
     * 用于前端菜单路由
     * 查询用户拥有的菜单列表，
     * 登录查询后，会放用户session中，
     * 此间更新代码用户感知不到，重新登录才能感知到，
     * 除非强制删除所有登录用户Session中的的Menu_List
     * @param loginUserId 用户ID
     * @return 菜单路由列表
     */
    public List<SysMenuVO> findMenuListByUserId(Long loginUserId) {
        List<SysMenuVO> sysMenuVOList = new ArrayList<>();
        //遍历角色列表，查询拥有的权限码
        Set<String> rolePermFlagSet = getRolePermFlag(loginUserId);
        SaSession userSession = StpUtil.getSessionByLoginId(loginUserId);
        if (rolePermFlagSet.contains(Constants.SUPER_ADMIN)) {
            //超级管理员拥有所有菜单. 且不带缓存，每次刷新都会请求菜单
            sysMenuVOList = menuService.selectFrontMenuAll();
        }else {
            for (String roleFlag : rolePermFlagSet) {
                //roleSession.get 方法有Sa-token提供缓存
                List<SysMenuVO> list = userSession.get(SESSION_KEY_MENU_LIST, () -> {
                    return menuService.selectFrontMenusByRoleName(roleFlag);     // 从数据库查询这个角色所拥有的权限列表
                });
                sysMenuVOList.addAll(list);
            }
        }
        return sysMenuVOList;
    }
}
